#include "miniopt.h"

extern void usage();
extern void version();

//==================================================================================

static bool eq( WCHAR* &pw, const char *opt )
{
   if ( !opt )
      return false;

   int i;
   for ( i = 0; opt[i]; ++i ) {
      if ( (WCHAR)opt[i] != pw[i] ) return false;
   }
   if ( !pw[i] ) { pw = NULL; return true; } // empty long option
   if ( L'=' != pw[i] ) return false;        // not equal
   pw[i] = L'\0'; pw += i + 1; return true;  // long option with value
}

//==================================================================================

unsigned Opt::wtou( const WCHAR *pw, unsigned base, unsigned *plen )
{
   unsigned val = 0, ndig = 0, mlen = plen ? *plen : 0;
   if ( 0 == base ) { base = 10; if ( L'0' == *pw ) base = 8; }
   for (;;) {
      unsigned dig = 100;
      if      ( *pw >= L'0' && *pw <= L'9' ) dig = (int)(*pw - L'0');
      else if ( *pw >= L'a' && *pw <= L'z' ) dig = (int)(*pw - L'a') + 10;
      else if ( *pw >= L'A' && *pw <= L'Z' ) dig = (int)(*pw - L'A') + 10;
      if ( dig >= base ) break;
      val = val *base + dig;
      ++ndig; ++pw;
      if ( mlen > 0 && ndig >= mlen ) break;
   }
   if (plen) *plen = ndig;
   return val;
}

int Opt::wtoi( const WCHAR *pw )
{
   if ( !pw ) return -0x7fffffff-1; // INT_MIN
   int sign = + 1, base = 10;

   if ( L'+' == *pw ) ++pw;
   else if ( L'-' == *pw ) { ++pw; sign = -1; }
   else if ( L'0' == *pw ) {
      ++pw; base = 8;
      if      ( L'x' == *pw || L'X' == *pw ) { ++pw; base = 16; }
      else if ( L'b' == *pw || L'B' == *pw ) { ++pw; base =  2; }
      else if ( L'z' == *pw || L'Z' == *pw ) { ++pw; base = 36; }
   }
   return sign * (int)wtou(pw, (unsigned)base, NULL);
}

//==================================================================================

bool Opt::getargs( int& argc, WCHAR** argv )
{
   WCHAR *cmd = Win::strdup(::GetCommandLineW());

   int ma = argc, na = 0;
   if ( !cmd || !cmd[0] )
   { argc = na; return false; }

   WCHAR *pw = cmd;
   for (;;)
   {
      while ( *pw && *pw <= L' ' ) ++pw;
      if ( !*pw || na >= ma ) break;

      WCHAR *po = argv[na++] = pw; WCHAR q = L'\0';
      for (;;) {
         if ( !q && (L'"' == *pw || L'\'' == *pw) ) { q = *pw; ++pw; continue; }
         if ( q && q == *pw ) { q = L'\0'; ++pw; continue; }
         if ( !*pw ) { *po = L'\0'; break; }
         if ( *pw <= L' ' && !q ) { ++pw; *po = L'\0'; break; }
         *po++ = *pw++;
      }
   }

   argc = na;
   return true;
}

//==================================================================================

bool Opt::getprog( WCHAR *execname, char *name, int maxlen, bool lower )
{
   WCHAR *pw = execname + ::lstrlenW(execname) - 1;
   while ( pw >= execname && *pw != L'/' && *pw != L'\\' ) --pw;
   ++pw;
   int nw = 0;
   while ( pw[nw] && pw[nw] != L'.' ) ++nw;
   BOOL defs = 0;
   int na = ::WideCharToMultiByte(CP_ACP, 0, pw, nw, name, maxlen-1, NULL, &defs);
   if ( na <= 0 || defs != 0 )
      return false;

   name[na] = '\0';
   if ( lower ) {
      while (--na >= 0) // only Latin letters lowercased...
      { if (name[na] >= 'A' && name[na] <= 'Z') name[na] = (char)(name[na]+'A'-'a'); }
   }
   return true;
}

//==================================================================================

int Opt::opt_err( int errcode, const WCHAR *opt, const WCHAR *val )
{
   const char *pe;
   switch ( errcode ) {
      case Err_Unknown_Lopt:   pe = "Unknown long";        break;
      case Err_Unknown_Sopt:   pe = "Unknown short";       break;
      case Err_Missed_Value:   pe = "Missed value for";    break;
      case Err_Wrong_Value :   pe = "Wrong value for";     break;
      case Err_Missed_Param:   pe = "Missed parameter(s)"; break;
      case Err_TooMany_Params: pe = "Too many parameters"; break;
      default:                 pe = "!!!unknown error!!!"; break;
   }

   Con::err(true);
   Con::f("ERR -- %s", pe);   
   if ( opt ) {
      Con::o(" option: ");
      if (L'-' == *opt) Con::o(opt); else { Con::o('-'); Con::o(opt,1); }
   }
   if ( val ) { Con::o("value: '"); Con::o(val); Con::o('\''); }
   Con::o("\r\nTry to use '--help' option\r\n");
   Con::err(false);

   return errcode;
}

//==================================================================================

int Opt::getopts( int argc, WCHAR** argv, Opt::option*options, int nopt, int start )
{
   bool used[MaxArgs], force_break = false;
   if ( start < 0 ) { start = -start; force_break = true; }   
      
   int i, j, rc;
   for ( i=0; i < argc; ++i ) used[i] = (i < start);

   for ( i = start; i < argc; ++i )
   {
      if ( used[i] ) continue;
      WCHAR *pw = argv[i];
      if ( *pw != L'-' || !pw[1] ) { if (force_break) break; else continue; }

      used[i] = true;
      ++pw;
      if ( *pw != L'-' ) { // short option(s)
         while ( *pw ) {
            for (j = 0; j < nopt; ++j) { 
               if ( *pw == (WCHAR)options[j].s_opt ) break;
            }
            if ( j >= nopt ) {
               if ( force_break ) { used[i] = false; i = argc; break; }
               return opt_err(Err_Unknown_Sopt, pw);
            }
            if ( options[j].i_o ) {
               *options[j].i_o = *options[j].i_o + 1;
            }
            else if ( options[j].f_o != NULL ) {
               ++i;
               if ( i >= argc )
               { return opt_err(Err_Missed_Value, pw); }
               used[i] = true;
               rc = options[j].f_o(argv[i]);
               if ( rc < 0 )
                  return opt_err(rc, pw, argv[i]);
            }
            ++pw;
         }
      }
      else { // long option
         ++pw;
         if ( !*pw ) break; // '--' found 

         for (j = 0; j < nopt; ++j) { 
            if ( eq(pw, options[j].l_opt) ) break;
         }
         if ( j >= nopt ) {
            if ( eq(pw, "help")   ) { usage();    return argc+1; } // --help
            if ( eq(pw, "version")) { version();  return argc+2; } // --version
            if ( eq(pw, "cp"     )) {            Con::setCP(pw); } // --cp=...
            // --cp=CPo --cp=CPi,CPo --cp=CPi,CPo,CPe

            if ( force_break ) { used[i] = false; break; }
            return opt_err(Err_Unknown_Lopt, argv[i]);
         }
         if ( options[j].i_o ) {
            *options[j].i_o = *options[j].i_o + 1;
         }
         else if ( options[j].f_o != NULL ) {
            rc = options[j].f_o(pw);
            if ( rc < 0 )
               return opt_err(rc, argv[i], pw);
         }
      }
   }

   i = 0;
   for (;;) // reorder: used arguments, unused arguments
   {
      while ( used[i] && i < argc ) ++i;
      if ( i >= argc ) return argc;       // 
      j = i + 1;                          // first unused returned
      while ( !used[j] && j < argc ) ++j; //
      if ( j >= argc ) return i;
      WCHAR *t = argv[j]; argv[j] = argv[j-1]; argv[j-1] = t;
      used[j-1] = true; used[j] = false;
   }   
}

//==================================================================================

BOOL Opt::norm_slash( const WCHAR *s, WCHAR *d, int dsz, int e_slash )
{
   int len = ::lstrlenW(s);
   if (len <= 0 || len >= dsz - 1 - e_slash)
   { ::SetLastError(ERROR_INVALID_NAME); return FALSE; }
   
   int nc = 0;;
   while (*s) {
      d[nc++] = (*s == L'/') ? L'\\' : *s;
      ++s;
      if (nc > 2 && d[nc-1] == L'\\' && d[nc-2] == L'\\') --nc;
   }
   
   if (e_slash > 0) {
      if (d[nc-1] != L'\\') d[nc++] = L'\\';
   }
   else if (e_slash < 0) {
      if (d[nc-1] == L'\\') --nc;
   }
         
   d[nc] = L'\0';
   return TRUE;
}

//----------------------------------------------------------------------------------

int Opt::root_length( const WCHAR *nm )
{
   int pref_len = 0, need_slash = 1;
   if (nm[1] != L':') {                                      /* 'D:'       */
      if (nm[0] != L'\\') return 0;
      if (nm[1] != L'\\') {
         if (nm[1] != L'?' || nm[2] != L'?' || nm[3] != L'\\') return 0;
         else pref_len = 4;                                  /* '\??\'     */
      }
      else {
         if (nm[2] == L'.' && nm[3] == L'\\') pref_len = 4;  /* '\\.\'     */
         else {
            pref_len = 2; need_slash = 2;                    /* '\\'       */
            if (nm[2] == L'?' || nm[3] == L'\\') {
               pref_len = 4; need_slash = 1;                 /* '\\?\'     */
               if (nm[4]==L'U' && nm[5]==L'N' && nm[6]==L'C' && nm[7]==L'\\')
               { pref_len = 8; need_slash = 2; }             /* '\\?\UNC\' */
            }
         }
      }
   }
   while (nm[pref_len]) {
      if (is_slash(nm[pref_len]) && --need_slash <= 0) break;
      else ++pref_len;
   }
   return pref_len;
}

//==================================================================================
